/*
 * This file is part of ddesktop: remote desktop system for iPad 
 *
 * Copyright (C) 2010 Kazuhiro Satoh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. if not, see <http://www.gnu.org/licenses/>.
 */


#include "stdafx.h"
#include "LibJpegEncoder.h"

#pragma unmanaged

#include <memory.h>
#include <windows.h>

typedef struct {
  struct jpeg_destination_mgr pub;      /* public fields */
  JOCTET *buffer;                                       /* start of buffer */
  int bufsize;                                          /* buffer size */
  int datacount;                                        /* finale data size */
} memory_destination_mgr;

typedef memory_destination_mgr *mem_dest_ptr;

/*----------------------------------------------------------------------------
  /  Initialize destination --- called by jpeg_start_compress before any data is actually written. */

METHODDEF(void)
init_destination (j_compress_ptr cinfo)
{
  mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
  dest->pub.next_output_byte = dest->buffer;
  dest->pub.free_in_buffer = dest->bufsize;
  dest->datacount=0;
}

/*----------------------------------------------------------------------------
  /  Empty the output buffer --- called whenever buffer fills up. */
METHODDEF(boolean)
empty_output_buffer (j_compress_ptr cinfo)
{
  mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
  dest->pub.next_output_byte = dest->buffer;
  dest->pub.free_in_buffer = dest->bufsize;

  return TRUE;
}

/*----------------------------------------------------------------------------
  /  Terminate destination --- called by jpeg_finish_compress
  /  after all data has been written.  Usually needs to flush buffer. */
METHODDEF(void)
term_destination (j_compress_ptr cinfo)
{
  /* expose the finale compressed image size */
  
  mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
  dest->datacount = dest->bufsize - dest->pub.free_in_buffer;
  
}

GLOBAL(void)
jpeg_memory_dest(j_compress_ptr cinfo, JOCTET *buffer,int bufsize)
{
  mem_dest_ptr dest;
  if (cinfo->dest == NULL) {    /* first time for this JPEG object? */
    cinfo->dest = (struct jpeg_destination_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                                  sizeof(memory_destination_mgr));
  }
  
  dest = (mem_dest_ptr) cinfo->dest;
  dest->bufsize=bufsize;
  dest->buffer=buffer;
  dest->pub.init_destination = init_destination;
  dest->pub.empty_output_buffer = empty_output_buffer;
  dest->pub.term_destination = term_destination;
}


METHODDEF(void)
debug_output_message (j_common_ptr cinfo)
{
  char buffer[JMSG_LENGTH_MAX];

  /* Create the message */
  (*cinfo->err->format_message) (cinfo, buffer);

  MessageBoxA(NULL, buffer, "JPEG Library Error", MB_OK | MB_ICONERROR);
}


#define SRC_BYTES_PER_PIXEL 3


namespace DDesktopServer{
	LibJpegEncoder::LibJpegEncoder()
	{
		m_data_buf = NULL;
		m_header_buf = NULL;
		m_data_length = 0;
		m_header_length = 0;
	}

	LibJpegEncoder::~LibJpegEncoder()
	{
		jpeg_destroy_compress(&m_cinfo);

		if(m_data_buf != NULL){
			delete [] m_data_buf;
		}
		if(m_header_buf != NULL){
			delete [] m_header_buf;
		}
	}

	bool LibJpegEncoder::Init(int max_width, int max_height)
	{
		m_data_buf_capacity = max_width * max_height * SRC_BYTES_PER_PIXEL;
		m_data_buf = new char[m_data_buf_capacity];
		if(m_data_buf == NULL){
			return false;
		}

		m_cinfo.err = jpeg_std_error(&m_jerr);
		m_cinfo.err->output_message = debug_output_message;
		jpeg_create_compress(&m_cinfo);

		return true;
	}

	const unsigned char* LibJpegEncoder::GetHeader(void) const
	{
		return (unsigned char*)m_header_buf;
	}


	const unsigned char* LibJpegEncoder::GetData(void) const
	{
		return (unsigned char*)m_data_buf;
	}


	int LibJpegEncoder::GetHeaderSize(void) const
	{
		return m_header_length;
	}

	int LibJpegEncoder::GetDataSize(void) const
	{
		return m_data_length;
	}


	bool LibJpegEncoder::Encode(char* data, int width, int height, int canvas_width, int canvas_height, int image_quality, int image_resolution)
	{
		JSAMPROW row_pointer;

		m_cinfo.image_width = width / image_resolution;
		m_cinfo.image_height = height / image_resolution;
		m_cinfo.input_components = SRC_BYTES_PER_PIXEL;
		m_cinfo.in_color_space = JCS_RGB;
		jpeg_set_defaults(&m_cinfo);
		jpeg_set_quality(&m_cinfo, image_quality, TRUE);
		jpeg_memory_dest(&m_cinfo, (JOCTET*)m_data_buf, m_data_buf_capacity);
		jpeg_start_compress(&m_cinfo, TRUE);

		{
			char* read_pt;
			char* write_pt;
			char tmp;
			for(int row = 0; row < height / image_resolution; row++){
				read_pt = &(data[(canvas_height - row * image_resolution - 1) * canvas_width * SRC_BYTES_PER_PIXEL]);
				write_pt = &(data[(canvas_height - row - 1) * canvas_width * SRC_BYTES_PER_PIXEL]);
				for(int col = 0; col < width / image_resolution; col++){
					tmp = *read_pt;
					write_pt[0] = read_pt[2];
					write_pt[1] = read_pt[1];
					write_pt[2] = tmp;
					write_pt += SRC_BYTES_PER_PIXEL;
					read_pt += SRC_BYTES_PER_PIXEL * image_resolution;
				}
			}
		}

		while (m_cinfo.next_scanline < (unsigned int)height / image_resolution) {
			row_pointer = (JSAMPROW)(data+((canvas_height - m_cinfo.next_scanline - 1) * canvas_width * SRC_BYTES_PER_PIXEL));
			jpeg_write_scanlines(&m_cinfo, &row_pointer, 1);
		}
		jpeg_finish_compress(&m_cinfo);
		mem_dest_ptr dest = (mem_dest_ptr)m_cinfo.dest;
		m_data_length = dest->datacount;

		return true;
	}


	bool LibJpegEncoder::Encode(char* data, int width, int height, int canvas_width, int canvas_height, int image_quality)
	{
		JSAMPROW row_pointer;

		m_cinfo.image_width = width;
		m_cinfo.image_height = height;
		m_cinfo.input_components = SRC_BYTES_PER_PIXEL;
		m_cinfo.in_color_space = JCS_RGB;
		jpeg_set_defaults(&m_cinfo);
		jpeg_set_quality(&m_cinfo, image_quality, TRUE);
		jpeg_memory_dest(&m_cinfo, (JOCTET*)m_data_buf, m_data_buf_capacity);
		jpeg_start_compress(&m_cinfo, TRUE);

		{
			char* pt;
			char tmp;
			for(int row = 0; row < height; row++){
				pt = &(data[(canvas_height - row - 1) * canvas_width * SRC_BYTES_PER_PIXEL]);
				for(int col = 0; col < width; col++){
					tmp = *pt;
					pt[0] = pt[2];
					pt[1] = pt[1];
					pt[2] = tmp;
					pt += SRC_BYTES_PER_PIXEL;
				}
			}
		}

		while (m_cinfo.next_scanline < (unsigned int)height) {
			row_pointer = (JSAMPROW)(data+((canvas_height - m_cinfo.next_scanline - 1) * canvas_width * SRC_BYTES_PER_PIXEL));
			jpeg_write_scanlines(&m_cinfo, &row_pointer, 1);
		}
		jpeg_finish_compress(&m_cinfo);
		mem_dest_ptr dest = (mem_dest_ptr)m_cinfo.dest;
		m_data_length = dest->datacount;

		return true;
	}


	// for debug
	//#include <fstream>

	bool LibJpegEncoder::WriteToFile(const char* file_name) const
	{
/*		std::ofstream fout(file_name, std::ios_base::binary);

		//fout.write((const char*)this->GetHeader(), this->GetHeaderSize());
		fout.write((const char*)this->GetData(), this->GetDataSize());

		fout.close();*/

		return true;
	}
}
